#include <cstddef>
#include <cstdlib>
#include <iostream>
#include <sys/types.h>

#include <boost/program_options.hpp>

#include "common/config/config.h"
#include "common/log/log.h"

#include "datenaro/database/data_gate.h"
#include "datenaro/database/migrator.h"

#include "entities/edge.h"
#include "entities/field.h"
#include "entities/field_kind.h"
#include "entities/issue.h"
#include "entities/issue_change.h"
#include "entities/issue_kind.h"
#include "entities/project.h"
#include "entities/state.h"
#include "entities/user.h"
#include "entities/version.h"
#include "entities/workflow.h"

#include "converter.h"
#include "data_gate.hpp"

namespace po = boost::program_options;

std::string configFilename(const po::variables_map& variables)
{
    if (variables.count("config"))
        return variables["config"].as<std::string>();

    return "config.ini";
}

void printHelp(const po::options_description& options)
{
    std::cout
        << "Использование: f2fb [ПАРАМЕТР]…" << std::endl
        << "Осуществляет перенос данных из базы farado в формат базы" << std::endl
        << "данных farado-binara, при необходимости создавая новую БД." << std::endl
        << "Значения всех идентификаторов сохраняются." << std::endl
        << std::endl;
    std::cout << options << std::endl;
}

po::options_description createOptions()
{
    po::options_description result("Допустимые параметры");
    result.add_options()(
        "help,h",
        "вывести справочное сообщение с перечнем разрешенных параметров\n"
    )(
        "config,c",
        po::value<std::string>(),
        "задать альтернативный путь к файлу конфигурации\n"
    )(
        "all,a",
        "конвертировать все элементы предметной области\n"
    )(
        "workflows,w",
        "конвертировать рабочие процессы\n"
    )(
        "states,s",
        "конвертировать состояния рабочих процессов\n"
    )(
        "edges,e",
        "конвертировать переходы рабочих процессов\n"
    )(
        "item-types,y",
        "конвертировать типы запросов\n"
    )(
        "field-types,f",
        "конвертировать типы полей\n"
    )(
        "users,u",
        "конвертировать пользователей\n"
    )(
        "projects,p",
        "конвертировать проекты\n"
    )(
        "phases,v",
        "конвертировать фазы проектов\n"
    )(
        "items,i",
        "конвертировать запросы\n"
    )(
        "item-histories,t",
        "конвертировать истории изменения запросов\n"
    )(
        "item-user-states,r",
        "конвертировать истории изменения состояний запросов пользователем\n"
    )(
        "item-fields,d",
        "конвертировать значения полей запросов\n"
    );
    return result;
}

void init(const po::variables_map& variables)
{
    initConfig(configFilename(variables));
    initLog(
        "f2fb",
        CONFIG.fileLog.logPath,
        CONFIG.fileLog.rotationSize,
        CONFIG.fileLog.needSourceLocation,
        CONFIG.fileLog.needThreadId,
        CONFIG.fileLog.dateTimeFormat,
        CONFIG.fileLog.severityLevel,
        CONFIG.consoleLog.needSourceLocation,
        CONFIG.consoleLog.needThreadId,
        CONFIG.consoleLog.dateTimeFormat,
        CONFIG.consoleLog.severityLevel
    );
}

int main(int argc, char* argv[])
{
    const auto options = createOptions();
    po::variables_map variables;

    try
    {
        po::store(po::parse_command_line(argc, argv, options), variables);
        po::notify(variables);
    }
    catch (const po::error& exception)
    {
        LOG_ERROR << "Ошибка параметров командной строки: " << exception.what();
        printHelp(options);
        return EXIT_FAILURE;
    }

    if (variables.count("help"))
    {
        printHelp(options);
        return EXIT_SUCCESS;
    }

    const bool all = variables.count("all");
    const bool workflows = variables.count("workflows");
    const bool states = variables.count("states");
    const bool edges = variables.count("edges");
    const bool itemTypes = variables.count("item-types");
    const bool fieldTypes = variables.count("field-types");
    const bool users = variables.count("users");
    const bool projects = variables.count("projects");
    const bool phases = variables.count("phases");
    const bool items = variables.count("items");
    const bool itemHistories = variables.count("item-histories");
    const bool itemUserStates = variables.count("item-user-states");
    const bool itemFields = variables.count("item-fields");

    init(variables);

    LOG_INFO << "---=== Обновления базы данных ===---";
    const auto migrationResult = Database::Migrator().run();
    if (0 != migrationResult)
    {
        LOG_FATAL << "Обновление базы данных провалено!";
        return migrationResult;
    }

    auto mineGate = Database::DataGate();
    auto theirsGate = F2FB::Database::DataGate("farado_database.sqlite");
    auto convert = [&mineGate](
                       auto theirsEntities,
                       auto converterFunction,
                       const std::string& caption
                   )
    {
        LOG_INFO << "---=== " << caption << " ===---";
        const auto entities = converterFunction(theirsEntities);
        const size_t size = entities.size();

        // TODO : Сохранять сущности не по одной, а пачкой.
        size_t current = 0;
        for (auto entity : entities)
        {
            if (mineGate.saveElement(entity))
            {
                LOG_INFO
                    << "Сущность " << caption << " #"
                    << entity->id()
                    << " успешно перенесена"
                    << " (" << ++current << "/" << size << ").";
            }
        }
    };

    if (all || workflows)
    {
        convert(
            theirsGate.run(&F2FB::Database::Workflow::entities),
            F2FB::Converter::workflows,
            "Workflow"
        );
    }

    if (all || states)
    {
        convert(
            theirsGate.run(&F2FB::Database::State::entities),
            F2FB::Converter::states,
            "State"
        );
    }

    if (all || edges)
    {
        convert(
            theirsGate.run(&F2FB::Database::Edge::entities),
            F2FB::Converter::edges,
            "Edge"
        );
    }

    if (all || itemTypes)
    {
        convert(
            theirsGate.run(&F2FB::Database::IssueKind::entities),
            F2FB::Converter::itemTypes,
            "ItemType"
        );
    }

    if (all || fieldTypes)
    {
        convert(
            theirsGate.run(&F2FB::Database::FieldKind::entities),
            F2FB::Converter::fieldTypes,
            "FieldType"
        );
    }

    if (all || users)
    {
        convert(
            theirsGate.run(&F2FB::Database::User::entities),
            F2FB::Converter::users,
            "User"
        );
    }

    if (all || projects)
    {
        convert(
            theirsGate.run(&F2FB::Database::Project::entities),
            F2FB::Converter::projects,
            "Project"
        );
    }

    if (all || phases)
    {
        convert(
            theirsGate.run(&F2FB::Database::Version::entities),
            F2FB::Converter::phases,
            "Phase"
        );
    }

    if (all || items)
    {
        convert(
            theirsGate.run(&F2FB::Database::Issue::entities),
            F2FB::Converter::items,
            "Item"
        );
    }

    if (all || itemHistories)
    {
        convert(
            theirsGate.run(&F2FB::Database::IssueChange::entities),
            F2FB::Converter::itemHistories,
            "ItemHistory"
        );
    }

    if (all || itemUserStates)
    {
        convert(
            theirsGate.run(&F2FB::Database::IssueChange::entities),
            F2FB::Converter::itemUserStates,
            "ItemUserStates"
        );
    }

    if (all || itemFields)
    {
        convert(
            theirsGate.run(&F2FB::Database::Field::entities),
            F2FB::Converter::itemFields,
            "ItemField"
        );
    }

    return 0;
}
